package core;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;

import core.domain.DataFile;
import core.domain.Part;
import core.domain.Part.PartStatus;
import core.domain.PeerDesciptor;
import core.domain.Torrent;

public class InMemoryTransferQueue implements ITransfersQueue {

	Map<Torrent, DataFile> store = null;

	// a nice cache, to keep track of the available parts for torrents of interest.
	Map<Torrent, Map<PeerDesciptor, List<Integer>>> resources = null;

	private static InMemoryTransferQueue instance = null;

	private InMemoryTransferQueue() {
		store = new ConcurrentHashMap<Torrent, DataFile>();
		resources = new ConcurrentHashMap<Torrent, Map<PeerDesciptor, List<Integer>>>();
	}

	public static synchronized InMemoryTransferQueue getInstance() {
		if (instance == null) {
			instance = new InMemoryTransferQueue();
		}
		return instance;
	}

	public void removeTorrentFromQueue(Torrent torrent) {
		store.remove(torrent);
	}

	public boolean addPartToTorrent(Torrent torrent, Part part) {
		//TODO: Please create assert class! this looks ugly!!!
		if (store.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		}
		return store.get(torrent).add(part);
	}

	public Part getIthPartForTorrent(Torrent torrent, int index) {
		//TODO: Please create assert class! this looks ugly!!!
		if (store.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		} else if (store.get(torrent).getPart(index).getPartStatus().equals(PartStatus.OUT_OF_BOUNDS)) {
			throw new RuntimeException("Out of index part requested!");
		}
		return store.get(torrent).getPart(index);
	}

	public List<Integer> missingPartsFor(Torrent torrent) {
		if (store.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		}
		return store.get(torrent).missingPieces();
	}

	public List<Integer> arrivedPiecesFor(Torrent torrent) {
		if (store.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		}
		return store.get(torrent).arrivedPieces();
	}

	public Map<PeerDesciptor, List<Integer>> getAvailabilityForTorrent(Torrent torrent) {
		if (resources.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		}
		return resources.get(torrent);
	}

	public void addAvailability(Torrent torrent, PeerDesciptor peer, List<Integer> availableParts) {
		Map<PeerDesciptor, List<Integer>> map = resources.get(torrent);
		if (map == null) {
			map = new ConcurrentHashMap<PeerDesciptor, List<Integer>>();
			resources.put(torrent, map);
		}
		map.put(peer, availableParts);
	}

	public DataFile getCompleteDatafile(Torrent torrent) {
		if (isSeeding(torrent)) {
			return store.get(torrent); //leave in queue
		} else {
			return null;
		}
	}

	@Override
	public void addSeedingTorrentToQueue(DataFile dataFile) {
		store.put(dataFile.getTorrent(), dataFile);
	}

	public void addLeechingTorrentToQueue(Torrent torrent) {
		store.put(torrent, new DataFile(torrent));
	}

	public boolean isSeeding(Torrent torrent) {
		if (store.get(torrent) == null) {
			throw new RuntimeException("Unknown torrent!");
		}
		return store.get(torrent).isComplete();
	}

	public PeerDesciptor getRandomPeerForTorrent(Torrent torrentOfInterest, PeerDesciptor self) {
		Map<PeerDesciptor, List<Integer>> availabilityForTorrent = getAvailabilityForTorrent(torrentOfInterest);
		availabilityForTorrent.remove(self);
		int choosenPeerIndex = new Random().nextInt(availabilityForTorrent.keySet().size());
		PeerDesciptor randomPeer = (availabilityForTorrent.keySet().toArray(new PeerDesciptor[0]))[choosenPeerIndex];
		System.out.println(randomPeer + " was selected for part fetching");
		return randomPeer;
	}

	@Override
	public Part getRandomMissingPartFromPeerForTorrent(PeerDesciptor randomPeerForTorrent, Torrent torrentOfInterest) {
		//needed torrents missing parts' index
		List<Integer> missingPartsForTorrent = missingPartsFor(torrentOfInterest);
		if (missingPartsForTorrent.size() == 0) {
			System.out.println("no more missing parts found!");
			Part part = new Part(0);
			part.setPartStatus(PartStatus.OK);
			return part;
		}
		Collections.sort(missingPartsForTorrent, new Comparator<Integer>() {
			@Override
			public int compare(Integer arg0, Integer arg1) {

				return Math.random() > 0.5 ? -1 : 1;
			}
		});
		Queue<Integer> intQueue = new ArrayBlockingQueue<Integer>(missingPartsForTorrent.size(), true, missingPartsForTorrent);

		while (!intQueue.isEmpty()) {
			Integer indexForNeededPart = intQueue.poll();
			//checking if random peer have that index.
			System.out.println("checking if" + randomPeerForTorrent + "have index: #" + indexForNeededPart);
			for (Integer availablePartIndex : resources.get(torrentOfInterest).get(randomPeerForTorrent)) {
				if (availablePartIndex.equals(indexForNeededPart)) {
					System.out.println("	For " + torrentOfInterest + " @ " + randomPeerForTorrent + " match needed index: #" + availablePartIndex.intValue());
					return getIthPartForTorrent(torrentOfInterest, availablePartIndex); //my empty part is sent over
				}
			}
		}

		return null;
	}

}
